package com.allenyuan.carvingsocketclient;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.CharacterCodingException;
import java.util.concurrent.Executors;

public class AsyncSocketClient {
	
	private static AsynchronousChannelGroup asyncChannelGroup; 
	
	private AsynchronousSocketChannel asyncSocket = null;
	
	static {
		try {
			asyncChannelGroup = AsynchronousChannelGroup.withThreadPool(Executors.newFixedThreadPool(5));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
	}
	
	public interface ProgressListener {
		public void connected(boolean success);
		public void received(com.google.protobuf.Message message);
	}
	
	private ProgressListener progressListener;
		
	public void setProgressListener(ProgressListener progressListener) {
		this.progressListener = progressListener;
	}
	
	private com.google.protobuf.Message message;

	public void setMessage(com.google.protobuf.Message message) {
		this.message = message;
	}

	public class ConnectionHandler implements CompletionHandler<Void, AsynchronousSocketChannel>
	{

		@Override
		public void completed(Void result, AsynchronousSocketChannel attachment) {
			// TODO Auto-generated method stub
			if (progressListener != null) {
				progressListener.connected(true);
			}
			if (message != null) {
				send(message);
				ByteBuffer buffer = ByteBuffer.allocate(1024);
				asyncSocket.read(buffer, buffer, new ReceiveHandler());
			}
		}

		@Override
		public void failed(Throwable exc, AsynchronousSocketChannel attachment) {
			// TODO Auto-generated method stub
			if (progressListener != null) {
				progressListener.connected(false);
			}
			System.out.println("connect failed");
		}
		
	}
	
	public class ReceiveHandler implements CompletionHandler<Integer,ByteBuffer>
	{
		
		@Override
		public void completed(Integer result, ByteBuffer attachment) {
			// TODO Auto-generated method stub
			if (result > 0) {   
				attachment.flip();   
				try {
					com.google.protobuf.Message message = SocketStreamUtils.decode(attachment.array());
					attachment.compact();
					if (progressListener != null) {
						progressListener.received(message);
					}
					
				} catch (CharacterCodingException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				}
				asyncSocket.read(attachment, attachment, this);
			} else if (result == -1) {
				attachment = null;
			} 
			
		}

		@Override
		public void failed(Throwable exc, ByteBuffer attachment) {
			// TODO Auto-generated method stub
			
		}
		
	}
	
	public class SendHandler implements CompletionHandler<Integer,ByteBuffer>
	{

		@Override
		public void completed(Integer result, ByteBuffer attachment) {
			// TODO Auto-generated method stub
			System.out.println("send success");
		}

		@Override
		public void failed(Throwable exc, ByteBuffer attachment) {
			// TODO Auto-generated method stub
			System.out.println("send failed");
		}
		
	}
	
	
	public boolean connect(String server, int port){
		if (asyncSocket == null) {
			try {
				asyncSocket = AsynchronousSocketChannel.open(asyncChannelGroup);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		try {
			asyncSocket.setOption(StandardSocketOptions.TCP_NODELAY, true);  
			asyncSocket.setOption(StandardSocketOptions.SO_REUSEADDR, true);
			asyncSocket.setOption(StandardSocketOptions.SO_KEEPALIVE, true);
			asyncSocket.connect(new InetSocketAddress(server, port),  asyncSocket, new ConnectionHandler()); 
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return true;
	}
	
	
	public boolean send(com.google.protobuf.Message message) {
		if (asyncSocket == null) {
			return false;
		}
		
		try {
			ByteBuffer buffer = ByteBuffer.wrap(SocketStreamUtils.encode(message));
			asyncSocket.write(buffer, buffer, new SendHandler());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return false;
		}
		return true;
		
	}
	
	public AsyncSocketClient() {
		// TODO Auto-generated constructor stub
	}
	
}
